/*
 * Copyright (C) 2005-present, 58.com.  All rights reserved.
 * Use of this source code is governed by a BSD type license that can be
 * found in the LICENSE file.
 */

// 仅支持list与list对象的交互
// 不支持
// 自定义Iterable对象
// 不可变list
require("./iterable");

let oldProto = Array.prototype;
oldProto.__proto__ = Iterable.prototype;
Array.prototype = Object.create(oldProto);
Object.assign(Array.prototype, {
  cast: function () {
    return this;
  },
  __op_idx__: function (index) {
    return this[index];
  },
  __op_idxeq__: function (index, value) {
    this[index] = value;
  },
  add: function (value) {
    this.push(value);
  },
  addAll: function (iterable) {
    iterable.forEach((item) => this.push(item));
  },
  shuffle: function (random) {
    throw "Not Implemented: shuffle";
  },
  // indexOf / lastIndexOf / length
  indexWhere: function (test, start = 0) {
    for (let i = start; i < this.length; i++) {
      if (test(this[i])) {
        return i;
      }
    }
    return -1;
  },
  lastIndexWhere: function (test, start) {
    if (start === undefined) {
      start = this.length - 1;
    }
    for (let i = start; i >= 0; i++) {
      if (test(this[i])) {
        return i;
      }
    }
    return -1;
  },
  clear: function () {
    this.length = 0;
  },
  insert: function (index, element) {
    this.splice(index, 0, element);
  },
  insertAll: function (index, iterable) {
    let args = [index, 0].concat(iterable);
    this.splice.apply(this, args);
  },
  setAll: function (index, iterable) {
    if (
      index < 0 ||
      index > this.length ||
      index + iterable.length > this.length
    ) {
      throw "error";
    }
    for (
      let i = index, j = 0;
      i < this.length && j < iterable.length;
      i++, j++
    ) {
      this[i] = iterable[j];
    }
  },
  remove: function (value) {
    let idx = this.indexOf(value);
    if (idx > -1) {
      this.splice(idx, 1);
      return true;
    } else {
      return false;
    }
  },
  removeAt: function (index) {
    this.splice(index, 1);
  },
  removeLast: function () {
    this.length = this.length - 1;
  },
  removeWhere: function (test) {
    let i = 0;
    while (i < this.length) {
      if (test(this[i])) {
        this.splice(i, 1);
      } else {
        i++;
      }
    }
  },
  retainWhere: function (test) {
    let i = 0;
    while (i < this.length) {
      if (!test(this[i])) {
        this.splice(i, 1);
      } else {
        i++;
      }
    }
  },
  __op_add__: function (other) {
    return this.concat(other);
  },
  sublist: function (start, end) {
    return this.slice(start, end);
  },
  getRange: function (start, end) {
    return this.slice(start, end);
  },
  setRange: function (start, end, iterable, skipCount = 0) {
    if (
      start < 0 ||
      start >= this.length ||
      end < 0 ||
      end >= this.length ||
      start >= end
    ) {
      throw "error";
    }
    let length = end - start;
    if (skipCount + length >= iterable.length) {
      throw "error";
    }

    for (let i = end - 1, j = skipCount + length - 1; i >= start; i--, j--) {
      this[i] = iterable[j];
    }
  },
  removeRange: function (start, end) {
    if (
      start < 0 ||
      start >= this.length ||
      end < 0 ||
      end >= this.length ||
      start >= end
    ) {
      throw "error";
    }
    while (start < end) {
      this.splice(start, 1);
      end--;
    }
  },
  fillRange: function (start, end, fillValue) {
    if (start < 0 || start >= this.length || end < 0 || end >= this.length) {
      throw "error";
    }
    while (start < end) {
      this[start] = fillValue || null;
      start++;
    }
  },
  replaceRange: function (start, end, replacement) {
    if (
      start < 0 ||
      start >= this.length ||
      end < 0 ||
      end >= this.length ||
      start >= end
    ) {
      throw "error";
    }
    this.removeRange(start, end);
    this.insertAll(start, replacement);
  },
  asMap: function () {
    let res = {};
    for (let i = 0; i < this.length; i++) {
      res[i] = this[i];
    }
    return res;
  },
});

Object.defineProperties(Array.prototype, {
  // length
  first: {
    set: function (value) {
      this[0] = value;
    },
    get: function () {
      return this[0];
    },
  },
  last: {
    set: function (value) {
      this[this.length - 1] = value;
    },
    get: function () {
      return this[this.length - 1];
    },
  },
  reversed: {
    get: function () {
      return this.slice(0).reverse();
    },
  },
});

Array.filled = function (length, fill, { growable = false } = {}) {
  let res = new Array(length);
  for (let i = 0; i < length; i++) {
    res[i] = fill;
  }
  return res;
};

Array.empty = function ({ growable = false } = {}) {
  return [];
};

Array.from = function (elements, { growable = true } = {}) {
  return elements.slice(0);
};

Array.of = function (elements, { growable = true } = {}) {
  return elements.slice(0);
};

Array.generate = function (length, generator, { growable = true } = {}) {
  let res = new Array(length);
  for (let i = 0; i < length; i++) {
    res[i] = generator(i);
  }
  return res;
};

Array.unmodifiable = function (elements) {
  return elements.slice(0);
};

Array.castFrom = function (source) {
  return source;
};

Array.copyRange = function (target, at, source, start, end) {
  if (!start) {
    start = 0;
  }
  if (end === undefined) {
    end = source.length;
  }
  let length = end - start;
  if (target.length < at + length) {
    throw `Not big enough to hold ${length} elements at position ${at}`;
  }
  for (let i = length; --i >= 0; ) {
    target[at + i] = source[start + i];
  }
};

Array.writeIterable = function (target, at, source) {
  target.setAll(at, source);
};

global.List = Array;
